#!/usr/bin/env python
# -*- coding: utf-8 -*-

""" The GAEO app package. 

Classes:
    GaeoApp: A WSGI-compatible application class. See PEP-333
    
Functions:
    start_app: A shortcut to start gaeoapp.
"""

# Python stdlib imports
import os
import sys
import cgi
import logging
import urllib
import wsgiref.headers
import wsgiref.util
from Cookie import BaseCookie
import StringIO
import webob

# gaeo imports
import router

# App imports
import settings
import routes


def _trans_http_header_key(key):
    """ Translate the environment variable into http header's name. """
    
    ret = None
    
    if key == 'CONTENT_TYPE':
        ret = 'Content-Type'
    elif key == 'CONTENT_LENGTH':
        ret = 'Content-Length'
    elif key.startswith('HTTP_'):
        ret = key[5:].replace('_', '-').title()

    return ret

class GaeoRequest(webob.Request):
    """ 
    Represents a HTTP request.
    """
    request_body_tempfile_limit = 0

    uri = property(lambda self: self.url)
    query = property(lambda self: self.query_string)
    
    def __init__(self, environ=None, charset='UTF-8'):
        """ Create the request instance.
        
        Args:
            environ:
            charset
        """
        super(GaeoRequest, self).__init__(environ, charset=charset,
                                          unicode_errors='ignore', decode_param_names=True)

    def get_all(self, argument_name):
        """Returns a list of query or POST arguments with the given name.
    
        We parse the query string and POST payload lazily, so this will be a
        slower operation on the first call.
    
        Args:
          argument_name: the name of the query or POST argument
    
        Returns:
          A (possibly empty) list of values.
        """
        if self.charset:
            argument_name = argument_name.encode(self.charset)
    
        param_value = self.params.getall(argument_name)
    
        for i in xrange(len(param_value)):
            if isinstance(param_value[i], cgi.FieldStorage):
                param_value[i] = param_value[i].value
    
        return param_value
    
    def arguments(self):
        """Returns a list of the arguments provided in the query and/or POST.
    
        The return value is a list of strings.
        """
        return list(set(self.params.keys()))


class GaeoResponse(object):
    """
    Represents the HTTP response.
    """
    
    def __init__(self, body=None, charset='utf-8'):
        self._charset = charset
        self._wsgi_headers = []
        self.headers = wsgiref.headers.Headers(self._wsgi_headers)
        self.headers['Content-Type'] = 'text/html; charset=%s' % charset
        self.headers['Cache-Control'] = 'no-cache'
        self.cookies = {}
        self.status = (200, 'OK')
        self.out = StringIO.StringIO() # the output buffer.
        
    def set_status(self, status_code, message):
        self.status = (int(status_code), message)
        
    def clear(self):
        """ Clear the output buffer. """
        self.out.seek(0)
        self.out.truncate(0)
        
    def wsgi_write(self, start_response):
        """ Flush the buffer to the client. """
        body = self.out.getvalue()
        if isinstance(body, unicode):
            body = body.encode('utf-8')
        elif self.headers.get('Content-Type', '').endswith('; charset=utf-8'):
            try:
                body.decode('utf-8')
            except UnicodeError, e:
                logging.warning('Response written is not UTF-8: %s', e)

        self.headers['Content-Length'] = str(len(body))
        write = start_response('%s %s' % self.status, self._wsgi_headers)
        write(body)
        self.out.close()

class GaeoApp(object):
    """
    A WSGI-compatible application class.
    
    """
    __instance = None

    def __init__(self):
        """ Initialize the applications. """
        self.__instance = self
        self.__router = self._init_router()
        
    def __call__(self, environ, start_response):
        """ Called by WSGI. 
        
        Args:
            environ:
            start_response:
            
        Return:
            The response body
        """
        self.request = GaeoRequest(environ)
        self.response = GaeoResponse()
        
        self._dispatch()
        
        if self.response.cookies:
            for key, value in self.response.cookies.iteritems():
                cookie = ['%s=%s' % (key, value), 
                          'path=%s' % settings.SESSION_COOKIE_PATH]
                domain_tokens = self.request.host.split('.')
                domain = ''
                if len(domain_tokens) == 1:
                    if domain_tokens[0] != 'localhost':
                        domain = domain_tokens[0]
                else:
                    domain = self.request.host
                cookie.append('domain=%s' % domain)
                self.response.headers.add_header('Set-Cookie', ';'.join(cookie))
            
        self.response.wsgi_write(start_response)
        return ['']
    
    def _dispatch(self):
        """ Dispatch the request according to the request URI. """
        routing = self.__router.resolve(self.request.path)
        if routing is None or routing[0] is None:
            self.response.set_status(404, "Not Found")
        else:
            route = routing[0]
            
            # check if RESTful
            if routing[1] is not None:
                method = self.request.method
                route['action'] = routing[1].get(method, 'index')
                
            if 'action' not in route:
                route['action'] = 'index'
            
            params = {}
            for k, v in self.request.params.iteritems():
                params[k] = v
            
            params.update(route)
            action_controller = None
            try:
                module_name = '%s.controllers.%s' % \
                    (settings.APP_DIR.split('/')[-1], route['controller'])
                controller_name = route['controller'].title().replace('-', '')
                module = __import__(module_name,
                                    globals(),
                                    locals(),
                                    controller_name,
                                    -1)
                action_controller = module.__dict__.get(controller_name)
            except ImportError, e:
                logging.error('Cannot import the controller: %s', e)
                self.response.set_status(500, 'Internal Server Error')
            except Exception, e:
                if settings.DEBUG:
                    raise
                self.response.set_status(500, 'Internal Server Error')
            else:
                try:
                    # create the controller instance
                    ctrl = action_controller(self.request, self.response, params=params)
                    
                    # apply the global init
                    application_module = \
                        __import__('%s.controllers.bootstrap' % settings.APP_DIR,
                                   globals(),
                                   locals(),
                                   'Bootstrap',
                                   -1)
                    app_clz = application_module.__dict__.get('Bootstrap')
                    if app_clz not in action_controller.__bases__:
                        action_controller.__bases__ += (app_clz, )
                    ctrl.bootstrap()
                    
                    # invokes the action
                    action = getattr(ctrl, route['action'], None)
                    if action:
                        if ctrl.before_action() is not False:
                            action()
                            ctrl.after_action()
                            ctrl.complete()
                    else:
                        # TODO: No action (404 ?)
                        pass
                    
                except Exception, e:
                    if settings.DEBUG:
                        raise
                    self.response.set_status(500, 'Internal Server Error')
                    
    @property
    def router(self):
        return self.__router
        
    def _init_router(self):      
        r = router.Router()
        for pattern, value in routes.ROUTINGS:
            matches = value.get('regex', [])
            params = value.get('params', {})
            r.add(router.RoutingRule(pattern, *matches, **params))
        return r


def _start_response(status, headers, exc_info=None):
    """ The start_response callback for WSGI app. (PEP-333) """
    
    if exc_info:
        raise exc_info[0], exc_info[1], exc_info[2]
    
    print 'Status: %s' % status
    for key, val in headers:
        print '%s: %s' % (key, val)
    print
    return sys.stdout.write


def start_app(app):
    """ A shortcut to start the GaeoApp instance.
    
    Args:
        app: The GaeoApp instance.
    """
    env = dict(os.environ)
    env["wsgi.input"] = sys.stdin
    env["wsgi.errors"] = sys.stderr
    env["wsgi.version"] = (1, 0)
    env["wsgi.run_once"] = True
    env["wsgi.url_scheme"] = wsgiref.util.guess_scheme(env)
    env["wsgi.multithread"] = False
    env["wsgi.multiprocess"] = False
    
    response_data = app(env, _start_response)
    if response_data:
        for data in response_data:
            sys.stdout.write(data)
    
    
